iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
自我挑戰組

馬克的軟體架構小筆記系列 第 18

30-18 之 DataSource Layer- DataMapper

  • 分享至 

  • xImage
  •  

這一篇文章我們將要談談常常聽到的 DataMapper 這個東西,應該是有不少人在一些 ORM 的 libary 中有聽到這東西。

什麼是 DataMapper 呢 ?

A layer of Mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself.

我個人的理解為 :

這個模式的理念在於,將『 domain 』從『 persistence layer ( 可以想成資料庫存取 ) 』分離出來,也就是說你的 domain model 實體中沒有所謂的資料庫處理這一塊。

然後他大部份的使用時機為 :

複雜的業務,且 DataMapper 都會與 Domain Layer 的 Domain Model 一起用

然後有一些重點要注意一下 :

  • 查詢資料時,通常會在 app 層呼叫 mapper 來取得資料,然後在交給 domain model 來操作流程。
  • 有些情況 domain 會呼叫 mapper 來取得到資料,但作者建議使用 Lazy Loading 與 Separated Interface 來處理這件事,因為他想將 domain 與 mapper 的依賴關係降到最低。( 這兩個名詞之後會提到 )
  • application 層通常可以有多個 mapper,但如果你有使用 Metadata Mapping ( 有緣分會說 ) 則可以只有一個。

接下來我覺得直接看範例,會比較能理解做啥。

範例

https://ithelp.ithome.com.tw/upload/images/20211003/20089358683qGGQwZO.png

這個範例業務是這樣的 :

有個用戶註冊,但是需要檢查該員工的公司是否已滿額,並且也要 18 歲才能 ( 不能是童工啊 ),但如果是 VIP 公司則沒限制。

// Domain Layer  =======================================
class PersonDomain{
    id: string
    age: number
    name: string
    company: string
    constructor(id:string, age: number, name: string,company: string){
        this.id = id
        this.age = age
        this.name = name
        this.company = company
    }
    isAdult(): boolean{
        return this.age >= 18
    }
    isVIP(): boolean{
        return ['HAHOW','GOOGLE','KKBOX'].includes(this.company)
    }
}

interface IPersonMapper{
    findById(id: string): PersonDomain
    findByCompany(company: string): PersonDomain[]
    insert(person: PersonDomain): void
    update(person: PersonDomain): void
    delete(person: PersonDomain): void
}

// DataSource Layer =======================================

function _mockExecuteSelectSql(sql){
    return [
        {
            id: '1',
            name: 'mark',
            age: 18,
            company: 'HAHOW'
        }
    ] 
}

class PersonMapper implements IPersonMapper {
    findById(id: string): PersonDomain{
        const resultSet: any = _mockExecuteSelectSql(`SELECT * FROM person WHERE id=${id}`)
        const result = this.doLoad(resultSet)
        return result[0]
    }
    findByCompany(company: string): PersonDomain[]{
        console.log('Connect to db for find')
        const resultSet: any = _mockExecuteSelectSql(`SELECT * FROM person WHERE company=${company}`)
        const result = this.doLoad(resultSet)
        return result
    }
    insert(person: PersonDomain): void{

    }
    update(person: PersonDomain): void{

    }
    delete(person: PersonDomain): void{

    }
    private doLoad(resultSet: any): PersonDomain[]{
        const result = []
        for (const data of resultSet) {
            result.push(new PersonDomain(data.id, data.age, data.name, data.company))
        }
        return result
    }
}

// Application Layer  =======================================
const personMapper = new PersonMapper()
const newPerson: PersonDomain = new PersonDomain(null, 18, 'Mark', 'HAHOW')
if(!newPerson.isVIP()){
   const personsInCompany: PersonDomain[] = personMapper.findByCompany('HAHOW') 
	 if(personsInCompany.length >= 10) throw Error('公司人數已達限制')
}

if(!newPerson.isAdult()) throw Error('要成年才能註冊喔')
personMapper.insert(newPerson)

小總結

這個知識點可以用來解釋什麼現象

DataMapper 是為了要將『 資料儲存 』與 『 Domain Model 』分開而誕生,那反過來思考,如果合在一起會變成怎麼樣呢 ?

我覺得比較大的問題是,很多的資料庫 query 不是為了該 domain 而產生的,例如在實務上我是有為了處理 N+1 的問題來抓某些資料,或是為了其它 domain 來抓資料,但那個 domain 需要的資料與原本的 domain 不相同,這幾種情況。這樣如果分離,就不用被綁死在 domain。

這個知識點可以和以前的什麼知識連結呢 ?

之前開發時有需要使用到 TypeORM,然後我看了現在就有個疑問 。

TypeORM - Active Record vs Data Mapper

你點進去,然後看這一段範例碼,是不是很像我們上面範例的 application 層那的程式碼,然後我的疑問是 :

Repository 就是指 DataMapper 嗎 ?

這個問題我們下集待續。

const userRepository = connection.getRepository(User);

// example how to save DM entity
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.isActive = true;
await userRepository.save(user);

// example how to remove DM entity
await userRepository.remove(user);

// example how to load DM entities
const users = await userRepository.find({ skip: 2, take: 5 });
const newUsers = await userRepository.find({ isActive: true });
const timber = await userRepository.findOne({ firstName: "Timber", lastName: "Saw" });

我要如何運用這個知識點 ?

事實上我還不確定要如何運用呢 ~ 因為有幾個問題我還有點困惑 :

  • Domain Model 感覺就是只處理 domain 的運算邏輯,其它的 i/o 與資料庫操作,感覺都是在 application 透過 mapper 來處理,我不確定我的理解這樣對不對。
  • 那這樣如果有 cache 要進行,是不是應該會寫在 mapper 又或是 application 呢 ?
  • 而且我發現我好多邏輯寫在 controller,還真有點不確定對不對呢,但我雄雄想到這個應該是之前有提到的 Service 部份。

參考資料


上一篇
30-17 之 DataSource Layer - Active Record
下一篇
30-19 之 Domain Layer - Repository
系列文
馬克的軟體架構小筆記29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言